/* ==============================================================================
*
*   Copyright (c) 2010, Pierre Favier
*   All rights reserved.
*
*   NTasking is free software: you can redistribute it and/or modify it
*   under the terms of the GNU General Public License as published by the
*   Free Software Foundation.
*
*   Redistribution and use in source and binary forms, with or without
*   modification, are permitted provided that the following conditions
*   are met:
*
*   -   Redistributions of source code must retain the above copyright notice,
*       this list of conditions and the following disclaimer.
*
*   -   Redistributions in binary form must reproduce the above
*       copyright notice, this list of conditions and the following
*       disclaimer in the documentation and/or other materials
*       provided with the distribution.
*
*   THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
*   WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
*   OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
*   DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
*   BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
*   EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
*   TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
*   DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
*   ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
*   TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
*   THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
*   SUCH DAMAGE.
*
* ============================================================================== */

using System;
using NTasking.ValidationTests.ACATS_3_0.Support;
using NUnit.Framework;
using Rhino.Mocks;
using System.Threading;

namespace NTasking.ValidationTests.ACATS_3_0.C9
{
    [TestFixture(Description = "")]
    public class C954A03 : ValidationTest
    {

        //      Declare a protected type which simulates a printer device driver
        //      (foundation code).
        //
        //      Declare a task which simulates a printer server for multiple printers.
        //
        //      For the protected type, declare an entry with a barrier that is set
        //      false by a protected procedure (which simulates starting a print job
        //      on the printer), and is set true by a second protected procedure (which
        //      simulates a handler called when the printer interrupts, indicating
        //      that printing is done).
        //
        //      For the task, declare an entry whose corresponding accept statement
        //      contains a call to first protected procedure of the protected type
        //      (which sets the barrier of the protected entry to false), followed by
        //      a requeue with abort to the protected entry. Declare a second entry
        //      which does nothing.
        //
        //      Declare a "requesting" task which calls the printer server task entry
        //      (and thus executes the requeue). Verify that, following the requeue,
        //      the requesting task remains blocked. Call the second entry of the
        //      printer server task (the acceptance of this entry call verifies that
        //      the requeue statement completed the entry call by the requesting task.
        //      Call the second protected procedure of the protected type (the
        //      interrupt handler) and verify that the protected entry completes for
        //      the requesting task (which verifies that the requeue statement queued
        //      the first task object to the protected entry).


        const double LongEnough = Impdef.SwitchToNewTask;
        

        private static PrinterServer printServer;
        private static Interlock2 interlock;
        private static bool taskCompleted = false;


        private class PrinterServer : ValidationTestTask
        {

            // Simulate a system with multiple printers. The entry Print requests
            // that data be printed on the next available printer. The entry call
            // is accepted when a printer is available, and completes when printing
            // is done.


            [Entry]
            internal Action<string> Print { get; private set; } // Test the requeue statement.

            [Entry]
            internal Action VerifyResults { get; private set; } // Artifice for test purposes.


            internal PrinterServer(IValidationTestResult result)
                : base(result)
            {
            }


            protected override void Body()
            {
                bool printersBusy = true;
                int index = 0;
                bool printAccepted = false;

                while (true)
                {

                    while (printersBusy)
                    {
                        Console.WriteLine("PrinterServer printer is busy");

                        printersBusy = false; // Exit loop if entry accepted

                        int index1 = index;

                        Ada.Select(s => s.
                            // Accepted immed. when printer is available
                            Call(Printer.Printers[index1].DonePrinting)().Then(() =>
                            {
                                Console.WriteLine("PrinterServer called Printer.DonePrinting");
                            }).
                        Else(() =>
                        {
                            Console.WriteLine("PrinterServer will try other printer");
                            // Entry not immed. accepted; keep looping.
                            index1 = (index1 + 1) % Printer.NumberOfPrinters;
                            printersBusy = true;
                        }));

                        index = index1;

                        Console.WriteLine("PrinterServer waiting");

                        // Allow other task to get control
                        Ada.Delay(Impdef.MinimumTaskSwitch);
                    }

                    // Value of Index at loop exit indicate avail. printer

                    // Wait for a print request or terminate

                    int index2 = index;

                    Select(s => s.

                        Accept(Print, (fileName) =>
                        {
                            Console.WriteLine("PrintServer accepting print job");

                            // Allow VerifyResults to be accepted
                            printAccepted = true;

                            // Begin printing on the available printer
                            Printer.Printers[index2].StartPrinting(fileName);

                            Console.WriteLine("PrintServer is printing, will requeue");

                            // Requeue caller so server task is free to accept other requests
                            RequeueWithAbort(Printer.Printers[index2].DonePrinting);

                        }).

                    Or.

                        //  Guard ensures that VerifyResults cannot be accepted
                        //  until after Print has been accepted. This avoids a
                        //  race condition in the main program.
                        When(() => printAccepted).
                            Accept(VerifyResults).      // Artifice for test purpose

                    Or.

                        Terminate

                    );

                    // Allow other tasks to get control
                    Ada.Delay(Impdef.MinimumTaskSwitch);
                }
            }
            protected override void Exception(Exception ex)
            {
                Console.WriteLine("Exception raised in PrinterServer task");
                Console.WriteLine(ex);
                Result.TestFailed();
            }
        }


        private class PrintRequest : ValidationTestTask
        {

            // Send a print request

            private const string MyFile = "MYFILE.DAT";


            internal PrintRequest(IValidationTestResult result)
                : base(result)
            {
            }


            protected override void Body()
            {
                try
                {
                    printServer.Print(MyFile);
                    interlock.Release();
                }
                catch (Exception ex)
                {
                    Console.WriteLine("Exception raised in PrintRequest task");
                    Console.WriteLine(ex);
                    Result.TestFailed();
                    throw;
                }
            }

        }


        public class Interlock2 : Protected
        {

            private bool locked = true;


            [Entry]
            public Action Wait { get; private set; }

            [ReadWriteOperation]
            public Action Release { get; private set; }


            public Interlock2()
            {
                Entry(Wait,
                    When: () => !locked,
                Is: () =>
                {
                    taskCompleted = true;
                });

                ReadWriteOperation(Release, Is: () =>
                {
                    locked = false;
                });
            }

        }


        [Test(Description =
            "Check that a requeue statement in an accept_statement with" +
            "parameters may requeue the entry call to a protected entry with no" +
            "parameters. Check that, if the call is queued on the new entry's" +
            "queue, the original caller remains blocked after the requeue, but" +
            "the accept_statement containing the requeue is completed." +
            "" +
            "Note that this test uses a requeue 'with abort,' although it does not" +
            "check that such a requeued caller can be aborted; that feature is" +
            "tested elsewhere.")]
        public void Test()
        {

            // To pass this test, the following must be true:
            //
            //    (A) The Print entry call made by the task Print_Request must be
            //        completed by the requeue statement.
            //    (B) Print_Request must remain blocked following the requeue.
            //    (C) Print_Request must be queued on the Done_Printing queue of
            //        Printer(1).
            //    (D) Print_Request must continue execution after Done_Printing is
            //        complete.
            //
            // First, verify (A): that the Print entry call is complete.
            //
            // Call the entry Verify_Results. If the requeue statement completed the
            // entry call to Print, the entry call to Verify_Results should be
            // accepted. Since the main will hang if this is NOT the case, make this
            // a timed entry call.

            IValidationTestResult result = Mocks.DynamicMock<IValidationTestResult>();
            PrintRequest printRequest = null;

            taskCompleted = false;

            Ada.Declare(() =>
            {
                Printer.Initialize();

                interlock = new Interlock2();
                printServer = new PrinterServer(result);
                printRequest = new PrintRequest(result);

                // Setup text expectations

                Expect.Call(() => result.TestFailed()).Repeat.Never();
                Expect.Call(() => result.TaskFinished(printServer));
                Expect.Call(() => result.TaskFinished(printRequest));

                Mocks.ReplayAll();
            },
            Begin: () =>
            {
                // Start test execution

                try
                {
                    Ada.Select(s => s.
                        // Accepted if requeue completed entry call to Print.
                        Call(printServer.VerifyResults)().
                    OrDelay(LongEnough).Then(() =>
                        {
                            Console.WriteLine("Requeue did not complete entry call");
                            result.TestFailed();
                        })
                    );

                    Ada.Delay(Impdef.SwitchToNewTask);

                    // Now verify (B): that Print_Request remains blocked following the
                    // requeue. Also verify that Done_Printing (the entry to which
                    // Print_Request should have been queued) has not yet executed.

                    if (Printer.Printers[0].IsDone())
                    {
                        Console.WriteLine("Target entry of requeue executed prematurely");
                        result.TestFailed();
                    }
                    else if (printRequest.Terminated)
                    {
                        Console.WriteLine("Caller did not remain blocked after the requeue");
                        result.TestFailed();
                    }

                    // Verify (C): that Print_Request is queued on the
                    // Done_Printing queue of Printer(1).
                    //
                    // Set the barrier for Printer(1).Done_Printing to true. Check
                    // that the Done flag is updated and that Print_Request terminates.

                    // Simulate a printer interrupt, signaling that printing is done
                    Printer.Printers[0].HandleInterrupt();

                    // The Done_Printing entry body will complete before the next
                    // protected action is called (Printer[0].IsDone).

                    if (!Printer.Printers[0].IsDone())
                    {
                        Console.WriteLine("Caller was not requeued on target entry");
                        result.TestFailed();
                    }

                    // Finally, verify (D): that Print_Request continues after Done_Printing
                    // completes.
                    //
                    // After Done_Printing completes, there is a potential race condition
                    // between the main program and Print_Request. The protected object
                    // Interlock is provided to ensure that the check of whether
                    // Print_Request continued is made *after* it has had a chance to do so.
                    // The main program waits until the statement in Print_Request following
                    // the requeue-causing statement has executed, then checks to see
                    // whether Print_Request did in fact continue executing.
                    //
                    // Note that the test will hang here if Print_Request does not continue
                    // executing following the completion of the requeued entry call.

                    // Wait until Print_Request is done
                    interlock.Wait();

                    if (!taskCompleted)
                    {
                        Console.WriteLine("Caller remained blocked after target entry released");
                        result.TestFailed();
                    }

                    // Wait for Print_Request to finish
                    while (!printRequest.Terminated)
                        Ada.Delay(Impdef.MinimumTaskSwitch);
                }
                catch (Exception ex)
                {
                    Console.WriteLine("_main_ threw exception " + ex.GetType().Name);
                    Console.WriteLine(ex);
                    throw;
                }
            });
        }

    }

}
